package com.qianfeng.smsplatform.strategy.filters.impl;

//
//                            _ooOoo_  
//                           o8888888o  
//                           88" . "88  
//                           (| -_- |)  
//                            O\ = /O  
//                        ____/`---'\____  
//                      .   ' \\| |// `.  
//                       / \\||| : |||// \  
//                     / _||||| -:- |||||- \  
//                       | | \\\ - /// | |  
//                     | \_| ''\---/'' | |  
//                      \ .-\__ `-` ___/-. /  
//                   ___`. .' /--.--\ `. . __  
//                ."" '< `.___\_<|>_/___.' >'"".  
//               | | : `- \`.;`\ _ /`;.`/ - ` : | |  
//                 \ \ `-. \_ __\ /__ _/ .-` / /  
//         ======`-.____`-.___\_____/___.-`____.-'======  
//                            `=---='  
//  
//         .............................................  
//                  佛祖镇楼                  BUG辟易  
//          佛曰:  
//                  写字楼里写字间，写字间里程序员；  
//                  程序人员写程序，又拿程序换酒钱。  
//                  酒醒只在网上坐，酒醉还来网下眠；  
//                  酒醉酒醒日复日，网上网下年复年。  
//                  但愿老死电脑间，不愿鞠躬老板前；  
//                  奔驰宝马贵者趣，公交自行程序员。  
//                  别人笑我忒疯癫，我笑自己命太贱；  
//  


import com.qianfeng.smsplatform.common.constants.CacheConstants;
import com.qianfeng.smsplatform.common.constants.InterfaceExceptionDict;
import com.qianfeng.smsplatform.common.model.Standard_Submit;
import com.qianfeng.smsplatform.strategy.api.CacheService;
import com.qianfeng.smsplatform.strategy.cache.LimitStrategyLocalCache;
import com.qianfeng.smsplatform.strategy.filters.FilterChain;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.util.List;
import java.util.Map;

/**
 * Created by Jackiechan on 2021/9/22/20:05
 * 这个是限流的过滤器,每一个客户针对某个手机号发送的短信的数量是有限制的,防止出现短信轰炸骚扰的情况
 *
 * @author Jackiechan
 * @version 1.0
 * @since 1.0
 */
@Component
public class LimitFilter implements FilterChain {

    private CacheService cacheService;

    private LimitStrategyLocalCache limitStrategyLocalCache;

    @Autowired
    public void setLimitStrategyLocalCache(LimitStrategyLocalCache limitStrategyLocalCache) {
        this.limitStrategyLocalCache = limitStrategyLocalCache;
    }

    @Autowired
    public void setCacheService(CacheService cacheService) {
        this.cacheService = cacheService;
    }

    @Override
    public boolean dealSms(Standard_Submit standard_submit) {
        System.err.println("限流策略执行了");
        //我们的平台会有不同的客户,这些不同的客户可能会对同一个手机号进行发送短信,问题是不同的客户是不是单独限流,比如一个手机号一天10条短信
        //通过其中某个客户给他发了十条,那么另外一个客户还能不能发,那么我们的业务上到底是如何定义的,是不同的客户共享同一个限流次数还是客户单独一个
        //限流可能会分很多种,比如一分钟可以发几条,五分钟能发几条,一小时能发几条,一天能发几条等等,会有很多不同的限制,有多少个?不清楚
        //问题来了,我们不知道我们的限制时间策略是什么,第二个限制多少条我们也不知道
        //问题来了,什么都不知道,写什么?做什么?
        //什么都不知道是因为我们没有数据,什么数据,比如限制什么时间的数据,每个时间限制多少的数据,这些东西我们都没有
        //是无论如何我们都没有这个数据还是暂时没有保存这个数据
        //假说我们有个地方可以保存我们的限流规则,比如我们约定什么代表时间,什么代表次数,我们约定好了只要后面保存的时候也按照这个规则保存数据就行,我们直接去对应的位置获取数据就可以了
        //限流规则我们如何定义,比如多少时间和多少次数如何定义出来,或者说如何绑定到一起,还有保存在什么地方

        //为了保证通用性,我们肯定会将这个限流数据保存到像redis之类的地方,这样可以在集群之间相互共享
        //redis中有什么数据格式方便我们保存这个时间和次数的数据,比如说hash格式的数据,我们以某个固定的key作为hash的key
        //然后以限制的时间作为field,以限制的次数做value,同样我们也可以反过来以次数作为filed,以时间作为value
        //比如有个hash 里面有60=1 300=3,我们发现比较诡异,这个key太容易变化 能不能通过score=60 value=1这种方式来记录时间和次数
        // 经过分析,我们想到了另外一个方案,我们知道zet是需要分数和内容的,我们将分数设置为限制的时间范围, 将value设置为限制的次数,这样我们从zet中每获取到的数据都是时间和次数的数据
        //而且这个时间是数字,随着限制的范围越来越大,从小大大的排序会让它越来越靠后,刚好也符合我们过滤的规则,就先判断最近的,后判断最远的

        //我们可以允许自定义限流策略,我们也可以定义系统的限流策略,现在我们使用的是系统的限流策略,我们可以定义某个key保存到redis中
//        List<Map> limitstrategySet = cacheService.getDataFromZsetByIndex(CacheConstants.CACHE_LIMITSTRATEGY_KEY, 0, -1);
        List<Map> limitstrategyList = limitStrategyLocalCache.getLimitstrategyList();
        int clientID = standard_submit.getClientID();//客户id
        String destMobile = standard_submit.getDestMobile();//手机号
        for (Map map : limitstrategyList) {
            Object score = map.get("score");
            Object value = map.get("value");
            System.err.println("score===" + score + "---value======>" + value);
            //假设上面的规则我们定义完了,也知道怎么获取数据了,假设已经拿到数据了
            //假设拿到了时间和条数,怎么计算,比如发现一个策略叫3分钟内发送一条,现在我们要发短信,怎么知道三分钟是不是曾经发过短信
            //理论上我们根据当前的时间,向前推限制的时间,在这两个时间范围内找到发送过的短信的数量,如果到达了限制的阈值则限流
            //怎么根据范围取值?说白了,就是有什么地方能让我们按照时间范围保存数据,并且可以按照时间范围进行查询. 凡是遇到如何xxx 怎么知道,怎么判断这类的问题
            //一定有一个地方保存了这些数据可以让我们进行判断,我们需要保存 什么时间发送的
            //我们每次发完短信都应该记录次数,然后每次发短信之前要先判断次数, 如果次数没有超,则发送短信,成功后保存次数
            // 我们现在需要根据时间范围去判断次数,所以每次发送短信的时间我们必须知道,不然没有办法按照时间判断
            //这个好像也是zset, zset分数可以用时间,value随便放个值,因为我们不需要value做操作, 因为一个时间就代表一次短信,所以时间有多少个就有多少短信
            //每次发送短信之前先用当前时间减去限制的时间,得到一个起点时间,以当前时间作为终点,判断这个两个时间范围内的数据都多少个
            //有多少个数据就代表发送了多少条短信,然后判断有没有到达阈值,没有就可以继续发送,到达就不能发送了
            //key是什么? 我们的限流是针对某个客户的某个手机号,所以可以以客户id加上目标手机号作为key

            long max = System.currentTimeMillis();//当前时间,也就是终点时间
            long min = (long) (max - (Double.parseDouble(score.toString()) * 1000));//起点时间
            Long times = cacheService.getSizeByScore(CacheConstants.CACHE_PREFIX_SMS_LIMIT + clientID + destMobile, min, max);//获取指定时间范围的发送过的短信的数量
            if (times >= Integer.parseInt(value.toString())) {
                //超出了阈值,不允许发送
                System.err.println("超出了发送的限制阈值,不能发送");
                standard_submit.setErrorCode(InterfaceExceptionDict.RETURN_STATUS_LIMIT_ERROR);
                standard_submit.setReportState(2);
                standard_submit.setDescription("当前手机号发送的次数超出了限制");
                return false;

            } else {
                //理论上来说如果能发送就给用户增加一次发送次数,但是我们后面有没有其他的过滤器不确定,万一有其他的过滤器将请求拦截下来,那么我们在这里添加的次数其实就是无效的,应该删除
                //但是删除又很麻烦,因为我们需要在后面的代码中删除,在什么地方删除不知道,删除什么数据不知道,所以在这里我们不添加数据
                //不添加数据怎么限流,我们可以在后面有一个统一的进行添加数据的地方,也可以是一个过滤器,这个过滤器放在最后面,主要用于确认计费,限流添加等操作
                //因为只要能到这个过滤器就说明前面的过滤器没有问题, 所以就可以添加限流次数
                //如果担心因为过滤器的处理需要时间,在这个时间范围内如果有多个请求过来,我们没有计算次数,导致超出了阈值,也可以提前进行一些简单的限制
                System.err.println("可以继续发送短信" + max);
            }
        }
        return true;
    }
}
